﻿using System;
using System.Globalization;
using System.IO;
using System.Net;
using System.Windows.Media.Imaging;

namespace SqueezeboxLib
{
    public class SqueezeboxPlayer
    {
        /// <summary>
        /// Gets the SqueezeboxPlayers ID (usually the MAC address)
        /// </summary>
        public string ID { get; private set; }

        /// <summary>
        /// Player IP and port.
        /// </summary>
        public string IP { get; set; }

        /// <summary>
        /// Player name.
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Player model.
        /// </summary>
        public string Model { get; set; }

        /// <summary>
        /// Whether a player is a known player model. Will be 0 for streaming connections.
        /// </summary>
        public bool IsPlayer { get; set; }

        /// <summary>
        /// Player display type. Not returned for streaming connections.
        /// </summary>
        public string DisplayType { get; set; }

        /// <summary>
        /// Whether the player can be powered off. This value is false for streaming connections.
        /// </summary>
        public bool CanPowerOff { get; set; }

        /// <summary>
        /// Connected state.
        /// </summary>
        public bool IsConnected { get; set; }

        /// <summary>
        /// Gets the server this squeezebox player is registered on
        /// </summary>
        public SqueezeboxServer Server { get; set; }

        public PlayerState State { get; private set; }

        public SqueezeboxSong CurrentSong { get; private set; }

        public SqueezeboxPlaylist Playlist { get; private set; }

        public Mixer Mixer { get; private set; }

        public Action<string> Send;

        public event EventHandler<MessageReceivedEventArgs> MessageReceived;

        public event EventHandler<SqueezeboxPlayerStateChangedEventArgs> StateChanged;
        public event EventHandler<UnknownCommandReceivedEventArgs> UnknownCommandReceived;
        public event EventHandler NameChanged;
        public event EventHandler SleepActivated;
        public event EventHandler Synced;
        public event EventHandler<PowerStateChangedEventArgs> PowerStateChanged;
        public event EventHandler<VolumeChangedEventArgs> MixerVolumeChanged;
        public event EventHandler<MutingChangedEventArgs> MixerMutingChanged;
        public event EventHandler<BassChangedEventArgs> MixerBassChanged;
        public event EventHandler<TrebleChangedEventArgs> MixerTrebleChanged;
        public event EventHandler ShowReceived;
        public event EventHandler DisplayReceived;
        public event EventHandler LinesPerScreenReceived;
        public event EventHandler<ButtonPressedEventArgs> ButtonPressed;
        public event EventHandler<IRCodeReceivedEventArgs> IRCodeReceived;
        public event EventHandler<IRModeChangedEventArgs> IRModeChanged;

        /// <summary>
        /// Creates a new Squeezebox Player
        /// </summary>
        /// <param name="playerID">Unique ID of the player, usually the MAC address.</param>
        /// <param name="server"></param>
        public SqueezeboxPlayer(string playerID, SqueezeboxServer server)
        {
            ID = playerID;

            Server = server;

            Send = server.Send;

            server.MessageReceived += OnServerMessageReceived;

            Mixer = new Mixer();

            Playlist = new SqueezeboxPlaylist(ID, Send, this);
            Playlist.RequestArtist();
            Playlist.RequestAlbum();

            CurrentSong = new SqueezeboxSong();
        }

        private void OnServerMessageReceived(object sender, MessageReceivedEventArgs e)
        {
            string[] split = e.Message.Split(' ');

            if(split[0] == ID)
            {
                PlayerCommand command;

                Enum.TryParse(split[1], true, out command);

                switch (command)
                {
                    case PlayerCommand.mixer:
                        double mixerVolume = double.Parse(split[3], NumberStyles.AllowDecimalPoint);
                        if(MixerVolumeChanged != null)
                        {
                            MixerVolumeChanged(this, new VolumeChangedEventArgs(mixerVolume));
                        }
                        break;
                    case PlayerCommand.unknown:
                    default:
                        if (UnknownCommandReceived != null)
                        {
                            UnknownCommandReceived(this, new UnknownCommandReceivedEventArgs(split[1]));
                        }
                        break;
                }

                if(MessageReceived != null)
                {
                    MessageReceived(this, new MessageReceivedEventArgs(e.Message));
                }
            }
        }

        public override string ToString()
        {
            return string.Format("{0} ({1})", Name, ID);
        }

        public BitmapFrame GetCurrentCoverArt()
        {
            string picUri = string.Format("http://{0}:9000/music/current/cover.jpg?player={1}", Server.IP, ID);

            // TODO: Make this async
            try
            {
                // Create the requests.
                WebRequest requestPic = WebRequest.Create(picUri);

                WebResponse responsePic = requestPic.GetResponse();
                
                using (new MemoryStream())
                {
                    return BitmapFrame.Create(responsePic.GetResponseStream(), BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
                }

            }
            catch (Exception)
            {
                return null;
            }

        }

        /// <summary>
        /// Queries the wireless signal strength for the player, range is 1 to 100.
        /// </summary>
        public void SignalStrength(string playerID)
        {
            Send(ID + " signalstrength ?");
        }

        /// <summary>
        /// Sets the name of the player.
        /// </summary>
        /// <param name="playerName"></param>
        public void SetPlayerName(string playerName)
        {
            Send(ID + " name " + playerName);
        }

        /// <summary>
        /// Queries the connected state of the player, 1 or 0 depending on the state of the TCP connection to the player. 
        /// SLIMP3 players, since they use UDP, always return 1.
        /// </summary>
        public void Connected(string playerID)
        {
            Send(ID + " connected ?");
        }

        /// <summary>
        /// Specifies a number of seconds to continue playing before powering off the player.
        /// </summary>
        /// <param name="time">pass 'null' to query the sleep time value.</param>
        public void Sleep(double? time)
        {
            Send(ID + " sleep " + (time == null ? "?" : time.ToString()));
        }

        /// <summary>
        /// Specifies the player to synchronise with the given playerid. 
        /// </summary>
        /// <param name="playerIndexOrPlayerID"></param>
        public void Sync(string playerIndexOrPlayerID)
        {
            Send(ID + " sync " + playerIndexOrPlayerID);
        }

        /// <summary>
        /// Specifies the player to unsynchronise with the given playerid. 
        /// </summary>
        public void Unsync()
        {
            Send(ID + " sync -");
        }

        /// <summary>
        /// Queries a comma separated list of sync groups members (IDs and names).
        /// </summary>
        public void SyncGroups()
        {
            Send("syncgroups ?");
        }

        /// <summary>
        /// Turns the player on or off.
        /// <para>
        /// For remote streaming connections, the command does nothing and the query always returns 1.
        /// </para>
        /// </summary>
        /// <param name="state"></param>
        public void Power(PowerState state)
        {
            string command = ID + " power ";

            switch (state)
            {
                case PowerState.Off:
                    command += "0";
                    break;
                case PowerState.On:
                    command += "1";
                    break;
                case PowerState.Query:
                default:
                    command += "?";
                    break;
            }

            Send(command);
        }

        /// <summary>
        /// Queries or sets the current volume setting for the player. 
        /// The scale is 0 to 100, in real numbers (i.e. 34.5 is valid). 
        /// If the player is muted, the volume is returned as a negative value.
        /// <para>
        /// Note that players display a 0 to 40 scale, that is, the 0..100 volume divided by 2,5.
        /// </para>
        /// </summary>
        /// <param name="level">0 .. 100|-100 .. +100|?</param>
        public void MixerVolume(string level)
        {
            Send(ID + " mixer volume " + level);
        }

        /// <summary>
        /// Mutes or unmutes the player.
        /// </summary>
        /// <param name="mode"></param>
        public void MixerMuting(MutingMode mode)
        {
            string command = ID + " mixer muting ";

            switch (mode)
            {
                case MutingMode.Unmute:
                    command += "0";
                    break;
                case MutingMode.Mute:
                    command += "1";
                    break;
                case MutingMode.Toggle:
                    command += "toggle";
                    break;
                case MutingMode.Query:
                default:
                    command += "?";
                    break;
            }

            Send(command);
        }

        /// <summary>
        /// Queries or sets the current bass setting for the player.
        /// This is only supported by SliMP3 and SqueezeBox (SB1) players.
        /// </summary>
        /// <param name="level">0 .. 100|-100 .. +100|?</param>
        public void MixerBass(string level)
        {
            Send(ID + " mixer bass " + level);
        }

        /// <summary>
        /// Queries or sets the current treble setting for the player.
        /// This is only supported by SliMP3 and SqueezeBox (SB1) players.
        /// </summary>
        /// <param name="level">0 .. 100|-100 .. +100|?</param>
        public void MixerTreble(string level)
        {
            Send(ID + " mixer treble " + level);
        }

        /// <summary>
        /// Queries or sets the current pitch setting for the player.
        /// This is only supported by SliMP3 and SqueezeBox (SB1) players.
        /// </summary>
        /// <param name="level">0 .. 100|-100 .. +100|?</param>
        public void MixerPitch(string level)
        {
            Send(ID + " mixer pitch " + level);
        }

        /// <summary>
        /// Displays a message on the player display for a given duration. 
        /// Various options are provided to customize the appearance of the message (font size, centering).
        /// If the mesage is too long to fit on the display, it scrolls. 
        /// <para>
        /// By default temporarily cancels any screensaver and increases the brightness to the maximum value. 
        /// </para>
        /// </summary>
        /// <param name="taggedParameters"></param>
        public void Show(params ShowTaggedParameter[] taggedParameters)
        {
            Send(ID + " show " + string.Join<ShowTaggedParameter>(" ", taggedParameters));
        }

        /// <summary>
        /// Specifies some text to be displayed on the player screen for a specified amount of time (in seconds).
        /// </summary>
        /// <param name="line1"></param>
        /// <param name="line2"></param>
        /// <param name="duration"></param>
        public void Display(string line1, string line2, double duration)
        {
            Send(ID + " display " + System.Web.HttpUtility.UrlEncode(line1)
                                  + " " + System.Web.HttpUtility.UrlEncode(line2)
                                  + " " + duration);
        }

        /// <summary>
        /// Queries how many lines of text can fit in the display, depending on its current setting or font.
        /// </summary>
        public void LinesPerScreen()
        {
            Send(ID + " linesperscreen ?");
        }

        /// <summary>
        /// Obtain the text that is currently displayed on the screen.
        /// </summary>
        public void DisplayCurrent(string playerID)
        {
            Send(ID + " display ? ?");
        }

        /// <summary>
        /// Provides access to the data currently on the display. 
        /// This differs from the "PlayerCurrentDisplay" method in that it returns the latest data sent to the display, 
        /// including any animation, double-size fonts, etc...
        /// </summary>
        public void DisplayNow(string playerID)
        {
            Send(ID + " displaynow ? ?");
        }

        /// <summary>
        /// Set or query the Squeezebox Server's internal player-specific preferences values.
        /// </summary>
        /// <param name="preferenceName">
        /// If you want to query/set a preference from an other namespace than "server" (eg. a plugin),
        /// you'll have to prepend the desired namespace to the preferenceName.
        /// </param>
        /// <param name="preferenceValue"></param>
        public void Pref(string preferenceName, string preferenceValue)
        {
            Send(ID + " playerpref " + preferenceName + " " + preferenceValue);
        }

        /// <summary>
        ///  Vlidate a Squeezebox Server's internal player-specific preference value without setting it.
        /// </summary>
        /// <param name="preferenceName">
        /// If you want to query/set a preference from an other namespace than "server" (eg. a plugin),
        /// you'll have to prepend the desired namespace to the preferenceName.
        /// </param>
        /// <param name="preferenceValue"></param>
        public void PrefValidate(string preferenceName, string preferenceValue)
        {
            Send(ID + " playerpref validate" + preferenceName + " " + preferenceValue);
        }

        /// <summary>
        /// Simulates a button press.
        /// </summary>
        /// <param name="buttonCode"></param>
        public void Button(SqueezeboxButtonCode buttonCode)
        {
            Send(ID + " button " + buttonCode);
        }

        /// <summary>
        /// Ssimulates an IR code.
        /// </summary>
        /// <param name="irCode">Valid IR codes are defined in the Default.map file.</param>
        /// <param name="time"></param>
        public void IR(string irCode, double time)
        {
            Send(ID + " ir " + irCode + " " + time);
        }

        /// <summary>
        /// Enables or disables IR processing for the player on or off.
        /// For remote streaming connections, the method does nothing and the query always returns 1.
        /// </summary>
        /// <param name="state"></param>
        public void IREnable(PowerState state)
        {
            Send(ID + " irenable " + (state == PowerState.Query ? "?" : ((int)state).ToString()));
        }

        /// <summary>
        /// Tells a Squeezebox 2 or newer player to connect to a different server address or to mysqueezebox.com. 
        /// <para>
        /// If the player is currently a member of a sync-group, then all players in the sync-group will be instructed to switch to the new server and reestablish the sync-group.
        /// </para>
        /// </summary>
        /// <param name="address">ip or url</param>
        public void Connect(string address)
        {
            Send(ID + " connect " + address);
        }

        /// <summary>
        /// Deletes the client/player from the server database.
        /// </summary>
        public void ClientForget()
        {
            Send(ID + " client forget");
        }

        /// <summary>
        /// Tells a Squeezebox 2 or newer player on another Squeezebox Server instance to disconnect from its server and connect to us. 
        /// </summary>
        /// <param name="address">ip or url</param>
        public void Disconnect(string address)
        {
            Send("disconnect " + ID + " " + address);
        }

        #region Playlist Commands

        /// <summary>
        /// Start playing the current playlist.
        /// </summary>
        /// <param name="fadeInSecs"></param>
        public void Play(double fadeInSecs)
        {
            Send(ID + " play " + fadeInSecs);
        }

        /// <summary>
        /// Stop playing the current playlist.
        /// </summary>
        public void Stop()
        {
            Send(ID + " stop");
        }

        /// <summary>
        /// Handle player pause state.
        /// </summary>
        /// <param name="mode"></param>
        /// <param name="fadeInSecs"></param>
        public void Pause(PauseMode mode, double fadeInSecs)
        {
            Send(ID + " pause " + (int)mode + " " + fadeInSecs);
        }

        /// <summary>
        /// Query the player state.
        /// Returns one of "play", "stop" or "pause". If the player is off, "mode ?" returned value is undefined.
        /// </summary>
        public void Mode()
        {
            Send(ID + " mode ?");
        }

        /// <summary>
        /// Change or query the current time of the current song.
        /// </summary>
        /// <param name="value">number|-number|+number|?</param>
        public void Time(string value)
        {
            Send(ID + " time " + value);
        }

        /// <summary>
        /// Query information about the song currently playing
        /// </summary>
        /// <param name="cliQuery"></param>
        public void CurrentSongQuery(SqueezeboxSong.CLIQuery cliQuery)
        {
            Send(ID + " " + cliQuery.ToString() + " ?");
        }

        #endregion
    }
}
